﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace SDF.Primitives
{
   /// <summary>
   /// Declares a dependency between a property and another.
   /// </summary>
   /// <remarks>
   /// NotifyPropertyChangedBase uses this attibute so that if property A is marked as dependending on property B,
   /// every time a PropertyChanged event is fired for B an event will also be fired for A.
   /// </remarks>
   [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
   public sealed class DependsUponAttribute : Attribute
   {
      #region Constructor

      /// <summary>
      /// Creates a new instance of the DependsUponAttribute for the given property.
      /// </summary>
      /// <param name="propertyName">The name of the property.</param>
      public DependsUponAttribute(string propertyName)
      {
         m_PropertyName = propertyName;
      }

      #endregion

      #region Properties

      /// <summary>
      /// The name of the property upon which the marked property depends upon.
      /// </summary>
      public string PropertyName 
      {
         get { return m_PropertyName; }
      }
      private string m_PropertyName;

      #endregion

      #region Static Fields

      /// <summary>
      /// This dictionary contains the list of all properties that directly depend on a given property.
      /// </summary>
      private static Dictionary<string, IEnumerable<string>> directDependancies = new Dictionary<string, IEnumerable<string>>();

      #endregion

      #region Static Methods

      /// <summary>
      /// Gets the list of properties that directly depend upon the given property.
      /// </summary>
      /// <param name="propertyName">The name of the property.</param>
      /// <param name="type">The type where the property is declared.</param>
      /// <returns>A list of property names.</returns>
      public static IEnumerable<string> GetDirectDependantProperties(string propertyName, Type type)
      {
         string fullPropertyName = GetFullPropertyName(propertyName, type);

         if (!directDependancies.ContainsKey(fullPropertyName))
         {
            List<string> result = new List<string>();
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
               object[] attributes = property.GetCustomAttributes(typeof(DependsUponAttribute), true);
               foreach (DependsUponAttribute attribute in attributes)
               {
                  if (attribute.PropertyName == propertyName)
                     result.Add(property.Name);
               }
            }
            directDependancies.Add(fullPropertyName, result.ToArray());
            return result;
         }

         return directDependancies[fullPropertyName];
      }

      /// <summary>
      /// Gets the full name of the property.
      /// </summary>
      /// <param name="propertyName">The name of the property.</param>
      /// <param name="type">The type where the property is declared.</param>
      /// <returns>A strinbg that uniquely identifies the property.</returns>
      private static string GetFullPropertyName(string propertyName, Type type)
      {
         return type.FullName + "." + propertyName;
      }


      /// <summary>
      /// Adds to a given list the properties that directly depend upon any of the given properties.
      /// </summary>
      /// <param name="propertyNames">The list of given properties.</param>
      /// <param name="type">The type where the properties are declared.</param>
      /// <returns>The list of properties that changed plus any other property that depends on one of those properties.</returns>
      public static IEnumerable<string> AddDirectDependantProperties(IEnumerable<string> propertyNames, Type type)
      {
         List<string> result = new List<string>();
         result.AddRange(propertyNames);

         foreach (string property in propertyNames)
            result.AddRange(GetDirectDependantProperties(property, type));

         return result.Distinct();
      }

      /// <summary>
      /// Gets a complete list of properties that depend upon the given property, directly or indirectly.
      /// </summary>
      /// <param name="propertyName">The name of the property.</param>
      /// <param name="type">The type where the property is declared.</param>
      /// <returns>A list with the name of all the properties that depend upon the given property, with the given property included.</returns>
      public static IEnumerable<string> GetDependantPropertiesRecursive(string propertyName, Type type)
      {
         string fullPropertyName = GetFullPropertyName(propertyName, type);

         IEnumerable<string> results = GetDirectDependantProperties(propertyName, type);
         if (results.Count() == 0)
            return results;

         List<string> list = results.ToList();
         List<string> newList = AddDirectDependantProperties(list, type).ToList();
         while (newList.Count > list.Count)
         {
            list = newList;
            newList = AddDirectDependantProperties(list, type).ToList();
         }
         
         return list;
      }

      #endregion

   }
}
